﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data.Activation;
using StyleMVVM.LanguageExtensions;

namespace StyleMVVM.Data.Collections
{
	public class WrapperCollection<TWrapper, TData> : IList<TWrapper>, INotifyCollectionChanged
		where TWrapper : IWrapperObject<TData>
	{
		private readonly IList<TData> dataList;
		private readonly ObservableCollection<TWrapper> wrappers;

		public WrapperCollection(IList<TData> dataList)
		{
			if (dataList == null)
			{
				throw new ArgumentNullException("dataList");
			}

			this.dataList = dataList;
			wrappers = new ObservableCollection<TWrapper>();

			dataList.Apply(x => wrapNewData(x, wrappers.Count));

			if (dataList is INotifyCollectionChanged)
			{
				((INotifyCollectionChanged)dataList).CollectionChanged += OnCollectionChanged;
			}
		}

		public IEnumerator<TWrapper> GetEnumerator()
		{
			return wrappers.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		public void Add(TWrapper item)
		{
			throw new NotImplementedException();
		}

		public void Add(TData item)
		{
			dataList.Add(item);
		}

		public void Clear()
		{
			dataList.Clear();
		}

		public bool Contains(TWrapper item)
		{
			return wrappers.Contains(item);
		}

		public void CopyTo(TWrapper[] array, int arrayIndex)
		{
			if (array.Length - arrayIndex < wrappers.Count)
			{
				throw new ArgumentOutOfRangeException("Array is to small to fit all wrappers");
			}
		}

		public bool Remove(TWrapper item)
		{
			throw new NotImplementedException();
		}

		public bool Remove(TData item)
		{
			return dataList.Remove(item);
		}

		public int Count
		{
			get { return wrappers.Count; }
		}

		public bool IsReadOnly
		{
			get { return true; }
		}

		public int IndexOf(TWrapper item)
		{
			return wrappers.IndexOf(item);
		}

		public void Insert(int index, TWrapper item)
		{
			throw new NotImplementedException();
		}

		public void Insert(int index, TData item)
		{
			dataList.Insert(index, item);
		}

		public void RemoveAt(int index)
		{
			dataList.RemoveAt(index);
		}

		public TWrapper this[int index]
		{
			get { return wrappers[index]; }
			set { throw new NotImplementedException(); }
		}

		public event NotifyCollectionChangedEventHandler CollectionChanged;

		protected virtual void OnCollectionChanged(object sender,
		                                           NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
		{
			switch (notifyCollectionChangedEventArgs.Action)
			{
				case NotifyCollectionChangedAction.Add:
					wrapNewItems(notifyCollectionChangedEventArgs.NewItems, notifyCollectionChangedEventArgs.NewStartingIndex);
					break;

				case NotifyCollectionChangedAction.Remove:
					removeWrappers(notifyCollectionChangedEventArgs.OldItems);
					break;

				case NotifyCollectionChangedAction.Replace:
					replaceWrappers(notifyCollectionChangedEventArgs);
					break;

				case NotifyCollectionChangedAction.Move:
					moveWrappers(notifyCollectionChangedEventArgs);
					break;

				case NotifyCollectionChangedAction.Reset:
					resetWrappedItems();
					break;
			}
		}

		private void replaceWrappers(NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
		{
			removeWrappers(notifyCollectionChangedEventArgs.OldItems);

			wrapNewItems(notifyCollectionChangedEventArgs.NewItems, notifyCollectionChangedEventArgs.NewStartingIndex);
		}

		private void moveWrappers(NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
		{
			if (notifyCollectionChangedEventArgs.OldItems.Count == 1)
			{
				wrappers.Move(notifyCollectionChangedEventArgs.OldStartingIndex, notifyCollectionChangedEventArgs.NewStartingIndex);
			}
			else
			{
				List<TWrapper> items = this.Skip(notifyCollectionChangedEventArgs.OldStartingIndex).
				                            Take(notifyCollectionChangedEventArgs.OldItems.Count).ToList();

				for (int i = 0; i < notifyCollectionChangedEventArgs.OldItems.Count; i++)
				{
					wrappers.RemoveAt(notifyCollectionChangedEventArgs.OldStartingIndex);
				}

				for (int i = 0; i < items.Count; i++)
				{
					wrappers.Insert(notifyCollectionChangedEventArgs.NewStartingIndex + i, items[i]);
				}
			}
		}

		private void removeWrappers(IList oldItems)
		{
			foreach (var item in oldItems)
			{
				if (item is TData)
				{
					TData oldData = (TData)item;

					foreach (TWrapper wrapper in wrappers)
					{
						if (wrapper.WrappedObject.Equals(oldData))
						{
							wrappers.Remove(wrapper);

							break;
						}
					}
				}
			}
		}

		private void resetWrappedItems()
		{
			wrappers.Clear();

			foreach (TData data in dataList)
			{
				wrapNewData(data, 0);
			}
		}

		private void wrapNewItems(IList newItems, int insertIndex)
		{
			foreach (var newItem in newItems)
			{
				if (newItem is TData)
				{
					wrapNewData((TData)newItem, insertIndex);

					insertIndex++;
				}
			}
		}

		private void wrapNewData(TData data, int index)
		{
			TWrapper newWrapper = ActivationService.Instance.Create<TWrapper>();

			if (newWrapper != null)
			{
				newWrapper.WrappedObject = data;

				wrappers.Insert(index, newWrapper);
			}
		}
	}
}